/*
 Copyright (c) 2013 Tianj. Guo
 
 
 */

#include "zen_vap_particle.h"
#include "zen_vap_helper.h"
#include "zen_vap_activity.h"
#include "zen_exception.h"

namespace Zen { namespace Vap {
	
	static Zen::Random s_rand;
	
	static inline float sAccMove(float p, float speed, float acc, float time)
	{
		return p + speed * time + acc * time * time * 0.5f;
	}
	static float sDefaultLerp(float s, float e, float v)
	{
		return s + (e - s) * (v * v);
	}
	static Color4f sDefaultColorLerp(Color4f s, Color4f e, float v)
	{
		return Color4fLerp(s, e, v);
	}
	/// @function Rf0 Rf01 Rf
	/// @brief return a number in the range [center-var, center+var].
	static float Rf0(float center, float var)
	{
		float r = center + (cosf(s_rand.nextf() * ZEN_F_PI)) * var;
		return r<0?0.f:r;
	}
	static float Rf01(float center, float var)
	{
		float r = center + (cosf(s_rand.nextf() * ZEN_F_PI)) * var;
		return r<0?0.f:(r>1.f?1.f:r);
	}
	static float Rf(float center, float var)
	{
		float r = center + (cosf(s_rand.nextf() * ZEN_F_PI)) * var;
		return r;
	}
}}

namespace Zen { namespace Vap {
	
	void Particles::setParticleTotalCount(size_t count)
	{
		m_total_count = count;
	}
	void Particles::setParticleStartColors(Color4f start)
	{
		m_start_color = start;
	}
	void Particles::setParticleStartColorsVarying(Color4f start_var)
	{
		m_start_color_var = start_var;
	}
	void Particles::setParticleEndColors(Color4f end)
	{
		m_end_color = end;
	}
	void Particles::setParticleEndColorsVarying(Color4f end_var)
	{
		m_end_color_var = end_var;
	}
	void Particles::setParticleStartSize(float start)
	{
		m_start_size = start;
	}
	void Particles::setParticleStartSizeVarying(float start_var)
	{
		m_start_size_var = start_var;
	}
	void Particles::setParticleEndSize(float end)
	{
		m_end_size = end;
	}
	void Particles::setParticleEndSizeVarying(float end_var)
	{
		m_end_size_var = end_var;
	}
	void Particles::setParticleLife(float life)
	{
		m_par_life = life;
	}
	void Particles::setParticleLifeVarying(float life_var)
	{
		m_par_life_var = life_var;
	}
	
	void Particles::setParticleStartAlpah(float start)
	{
		m_start_alpha = start;
	}
	void Particles::setParticleStartAlpahVarying(float start_var)
	{
		m_start_alpha_var = start_var;
	}
	void Particles::setParticleEndAlpah(float end)
	{
		m_end_alpha = end;
	}
	void Particles::setParticleEndAlpahVarying(float end_var)
	{
		m_end_alpha_var = end_var;
	}
	void Particles::setUpdateInterval(float interval)
	{
		m_update_interval = interval;
	}
	
	void Particles::setColorLerpFunction(std::function<Color4f(Color4f s, Color4f e, float v)> lerp)
	{
		if(lerp) m_color_lerp = lerp;
		else m_color_lerp = &sDefaultColorLerp;
	}
	
	void Particles::setAlphaLerpFunction(std::function<float(float s, float e, float v)> lerp)
	{
		if(lerp) m_alpha_lerp = lerp;
		else m_alpha_lerp = &sDefaultLerp;
	}
	void Particles::setSizeLerpFunction(std::function<float(float s, float e, float v)> lerp)
	{
		if(lerp) m_size_lerp = lerp;
		else m_size_lerp = &sDefaultLerp;
	}
	
	void  Particles::setEmitterLife(double sec)
	{
		m_emittor_life = sec;
	}
	
	double Particles::getEmitterLife()
	{
		return m_emittor_life;
	}
	void Particles::start()
	{
		if(m_update_action)
		{
			this->stopAction(m_update_action);
		}
		this->resetParticles();
		
		m_running_time = 0;
		
		auto call = [this](float interval, int)->bool {
			m_running_time += interval;
			this->updateParticles(interval);
			return true;
		};
		m_update_action = ActionCall::Create(call, 0, 0, m_update_interval);
		this->runAction(m_update_action);
	}
	void Particles::stop()
	{
		if(m_update_action != nullptr)
		{
			this->stopAction(m_update_action);
			m_update_action = nullptr;
		}
	}
	bool Particles::isRunning()
	{
		return m_update_action != nullptr;
	}
	
	void Particles::setTexture(SharedTexture texture)
	{
		m_texture = texture;
	}
	
	SharedTexture Particles::getTexture()
	{
		return m_texture;
	}
}}

namespace Zen { namespace Vap {
	
	ParticlesGravity::ParticlesGravity()
		{
	}
	
	void ParticlesGravity::setParticleEmitAngle(float randians)
	{
		m_emit_angle_var = randians;
	}
	void ParticlesGravity::setParticleEmitAngleVarying(float var)
	{
		m_emit_angle_var = var;
	}
	void ParticlesGravity::setParticleGravityValue(float acc)
	{
		m_gravity_acc = acc;
	}
	void ParticlesGravity::setParticleGravityAccVarying(float acc_var)
	{
		m_gravity_acc_var = acc_var;
	}
	
	void ParticlesGravity::setParticleGravityDirect(float r)
	{
		m_gravity_radians = r;
	}
	void ParticlesGravity::setParticleGravityDirectVarying(float r_var)
	{
		m_gravity_radians_var = r_var;
	}
	void ParticlesGravity::setParticleEmitSpeed(float speed)
	{
		m_emit_speed = speed;
	}
	void ParticlesGravity::setParticleEmitSpeedVarying(float speed_var)
	{
		m_emit_speed_var = speed_var;
	}
	void ParticlesGravity::setParticleEmitPosition(Vec2 pos)
	{
		m_emit_pos = pos;
	}
	void ParticlesGravity::setParticleEmitPositionVarying(Vec2 pos_var)
	{
		m_emit_pos_var = pos_var;
	}
}}

namespace Zen { namespace Vap {
	ParticlesGalaxy::ParticlesGalaxy()
		{
		m_radius_lerp = &sDefaultLerp;
	}
	
	void ParticlesGalaxy::setParticleEmitAngle(float rad)
	{
		m_emit_angle = rad;
	}
	void ParticlesGalaxy::setParticleEmitAngularSpeed(float av)
	{
		m_emit_av = av;
	}
	
	void ParticlesGalaxy::setParticleStartRadius(float r)
	{
		m_start_radius = r;
	}
	
	void ParticlesGalaxy::setParticleEndRadius(float r)
	{
		m_end_radius = r;
	}
	
	void ParticlesGalaxy::setParticleAngularAcc(float acc)
	{
		m_emit_acc = acc;
	}
	
	
	void ParticlesGalaxy::setParticleEmitAngleVar(float rad_var)
	{
		m_emit_angle_var = rad_var;
	}
	
	void ParticlesGalaxy::setParticleEmitAngularSpeedVar(float av_var)
	{
		m_emit_av_var = av_var;
	}
	
	void ParticlesGalaxy::setParticleStartRadiusVar(float r_var)
	{
		m_start_radius_var = r_var;
	}
	
	void ParticlesGalaxy::setParticleEndRadiusVar(float r_var)
	{
		m_end_radius_var = r_var;
	}
	
	void ParticlesGalaxy::setParticleAngularAccVar(float acc_var)
	{
		m_emit_acc_var = acc_var;
	}
	
	void ParticlesGalaxy::setRadiusLerpFunction(std::function<float (float, float, float)> lerp)
	{
		if(!lerp) m_radius_lerp = &sDefaultLerp;
		else m_radius_lerp = lerp;
	}
}}
namespace Zen { namespace Vap {
	
	bool Particles::updateParticles(float interval)
	{
		this->tryUpdateViewScale_();

		bool reuse = (m_emittor_life < 0) || (m_emittor_life > m_running_time);
		
		size_t dead = 0;
		for(size_t i = 0; i < m_vertexes.size(); ++i)
		{
			if(this->updateParticleAtIndex(i, interval)) continue;
			// else
			if(reuse) this->resetParticleAtIndex(i);
			else ++dead;
		}
		/**
		 if all dead stop.
		 */
		return (dead != m_vertexes.size());
	}
	
	void Particles::resetParticles()
	{
		this->tryUpdateViewScale_();

		m_vertexes.resize(m_total_count);
		for(size_t i = 0; i < m_total_count; ++i)
		{
			this->resetParticleAtIndex(i);
		}
	}
	
	void Particles::resetParticleAtIndex(size_t index)
	{
		auto & i = m_vertexes[index];
		i.color.red = Rf01(m_start_color.red, m_start_color_var.red);
		i.color.green = Rf01(m_start_color.green, m_start_color_var.green);
		i.color.blue = Rf01(m_start_color.blue, m_start_color_var.blue);
		i.color.alpha = Rf01(m_start_color.alpha, m_start_color_var.alpha) *
		Rf01(m_start_alpha, m_start_alpha_var);
		
		i.point_size = Rf01(m_start_size, m_start_size_var);
		i.coord.x = s_rand.nextf();
		i.coord.y = s_rand.nextf();

		m_is_vertexes_dirty = true;
	}
	bool Particles::updateParticleAtIndex(size_t index, float)
	{
		return (m_emittor_life < 0 || m_emittor_life > m_running_time);
	}
	
	void ParticlesGravity::resetParticles()
	{
		m_particles.resize(m_total_count);

		Particles::resetParticles();

		for(auto & i : m_particles) i.age = i.life * s_rand.nextf();
	}
	
	bool ParticlesGravity::updateParticles(float interval)
	{
		return Particles::updateParticles(interval);
	}
	void ParticlesGravity::resetParticleAtIndex(size_t index)
	{
		auto & i = m_particles[index];
		i.start_color.red = Rf01(m_start_color.red, m_start_color_var.red);
		i.start_color.green = Rf01(m_start_color.green, m_start_color_var.green);
		i.start_color.blue = Rf01(m_start_color.blue, m_start_color_var.blue);
		i.start_color.alpha = Rf01(m_start_color.alpha, m_start_color_var.alpha);
		
		i.end_color.red = Rf01(m_end_color.red, m_end_color_var.red);
		i.end_color.green = Rf01(m_end_color.green, m_end_color_var.green);
		i.end_color.blue = Rf01(m_end_color.blue, m_end_color_var.blue);
		i.end_color.alpha = Rf01(m_end_color.alpha, m_end_color_var.alpha);
		
		i.start_alpha = Rf01(m_start_alpha, m_start_alpha_var);
		i.end_alpha = Rf01(m_end_alpha, m_end_alpha_var);
		
		i.start_size = Rf0(m_start_size, m_start_size_var);
		i.end_size = Rf0(m_end_size, m_end_size_var);
		float r = Rf(m_emit_angle, m_emit_angle_var);
		float s = Rf(m_emit_speed, m_emit_speed_var);
		i.emit_speed.x = cosf(r) * s;
		i.emit_speed.y = sinf(r) * s;
		i.emit_position.x = Rf(m_emit_pos.x, m_emit_pos_var.x);
		i.emit_position.y = Rf(m_emit_pos.y, m_emit_pos_var.y);
		float gr = Rf(m_gravity_radians, m_gravity_radians_var);
		float ga = Rf(m_gravity_acc, m_gravity_acc_var);
		i.gravity.x = cosf(gr) * ga;
		i.gravity.y = sinf(gr) * ga;
		i.life = Rf0(m_par_life, m_par_life_var);

		i.age = 0;
		this->updateParticleAtIndex(index, 0);
	}
	
	bool ParticlesGravity::updateParticleAtIndex(size_t index, float interval)
	{
		auto & s = m_particles[index];
		
		if(s.age > s.life) return false;
		
		s.age += interval;
		float v = (s.age > s.life ? 1.f : s.age / s.life);
		
		auto & dot = m_vertexes[index];
		dot.coord.x = sAccMove(s.emit_position.x, s.emit_speed.x, s.gravity.x, s.age) / m_scale2.x;
		dot.coord.y = sAccMove(s.emit_position.y, s.emit_speed.y, s.gravity.y, s.age) / m_scale2.y;
		
		// --- set alpha color.size()
		dot.color = m_color_lerp(s.start_color, s.end_color, v);
		dot.color.alpha *= m_alpha_lerp(s.start_alpha, s.end_alpha, v);
		
		if(s.age > s.life) dot.point_size = 0;
		else dot.point_size = m_size_lerp(s.start_size, s.end_size, v);

		m_is_vertexes_dirty = true;
		return true;
	}
	
	void ParticlesGalaxy::resetParticleAtIndex(size_t index)
	{
		auto & info = m_particles[index];
		info.start_color.red = Rf01(m_start_color.red, m_start_color_var.red);
		info.start_color.green = Rf01(m_start_color.green, m_start_color_var.green);
		info.start_color.blue = Rf01(m_start_color.blue, m_start_color_var.blue);
		info.start_color.alpha = Rf01(m_start_color.alpha, m_start_color_var.alpha);
		
		info.end_color.red = Rf01(m_end_color.red, m_end_color_var.red);
		info.end_color.green = Rf01(m_end_color.green, m_end_color_var.green);
		info.end_color.blue = Rf01(m_end_color.blue, m_end_color_var.blue);
		info.end_color.alpha = Rf01(m_end_color.alpha, m_end_color_var.alpha);
		
		info.start_alpha = Rf01(m_start_alpha, m_start_alpha_var);
		info.end_alpha = Rf01(m_end_alpha, m_end_alpha_var);
		
		info.start_size = Rf0(m_start_size, m_start_size_var); //x; // / Activity::Get()->getViewScale();
		info.end_size = Rf0(m_end_size, m_end_size_var); // / m_scale2.y; // / Activity::Get()->getViewScale();
		info.life = Rf(m_par_life, m_par_life_var);
		
		info.start_radius = Rf(m_start_radius, m_start_radius_var);
		info.end_radius = Rf(m_end_radius, m_end_radius_var);
		info.emit_av = Rf(m_emit_av, m_emit_av_var);
		info.emit_acc = Rf(m_emit_acc, m_emit_acc_var);
		info.emit_angle = Rf(m_emit_angle, m_emit_angle_var);

		info.age = 0;

		this->updateParticleAtIndex(index, 0);
	}
	
	bool ParticlesGalaxy::updateParticleAtIndex(size_t index, float interval)
	{
		auto & s = m_particles[index];
		
		if(s.age > s.life) return false;
		
		s.age += interval;
		float v = (s.age > s.life ? 1.f : s.age / s.life);
		
		auto & dot = m_vertexes[index];
		
		float r = Zen::NumLerp(s.start_radius, s.end_radius, v);
		float rad = s.emit_angle + s.emit_av * s.age + s.emit_acc * s.age * s.age * 0.5f;
		dot.coord.x = r * cosf(rad) / m_scale2.x;
		dot.coord.y = r * sinf(rad) / m_scale2.y;
		
		// --- set alpha color.size()
		dot.color = m_color_lerp(s.start_color, s.end_color, v);
		dot.color.alpha = m_alpha_lerp(s.start_alpha, s.end_alpha, v);
		
		if(s.age > s.life) dot.point_size = 0;
		else dot.point_size = m_size_lerp(s.start_size, s.end_size, v);
		
		m_is_vertexes_dirty = true;

		return true;
	}
	void ParticlesGalaxy::resetParticles()
	{
		m_particles.resize(m_total_count);
		Particles::resetParticles();
		for(auto & i : m_particles) i.age = i.life * s_rand.nextf();
	}
	bool ParticlesGalaxy::updateParticles(float interval)
	{
		return Particles::updateParticles(interval);
	}
}}

namespace Zen { namespace Vap {
	
	Particles::Particles()
	: Node2d(), PartWithMatrix2d(false)
	{
		m_alpha_lerp = &sDefaultLerp;
		m_color_lerp = &sDefaultColorLerp;
		m_size_lerp = &sDefaultLerp;
		m_blend = eBlend::Add;
		m_color = Color4b(0xff8822ff);
		this->tryUpdateViewScale_();
		initR_();
	}
	bool Particles::tryUpdateViewScale_()
	{
		auto vs = Activity::Get()->getRealViewSize();
		if(this->m_scale2.x == vs.w) return false;

		this->setScale2_(vs.w, vs.h);

		return true;
	}
	void Particles::updateSizeRatio_() {
		auto x = 0.5f * m_point_scale;
		m_size_ratio = { x, x*(m_scale2.y/m_scale2.x) };
	}

	void Particles::setPointSizeScale(float scale)
	{
		if(m_point_scale == scale) return;
		m_point_scale = scale;
		m_is_size_dirty = true;
	}

	void Particles::draw()
	{
		if(!isRunning() || !m_vertexes.size()) return;
		
		this->updateMatrix();
		
		if(this->tryUpdateViewScale_())
		{
			m_is_size_dirty = true;
		}

		if(m_is_vertexes_dirty)
		
		this->drawR_();
	}
#if ZEN_DRAW_GLES
	void Particles::initR_()
	{
	}
	void Particles::drawR_()
	{
		if(m_is_size_dirty)
		{
			m_is_size_dirty = false;
			this->updateSizeRatio_();
		}
		if(m_is_vertexes_dirty)
		{
			m_is_vertexes_dirty = false;
			mRBuffer.v.updateBuffer(m_vertexes.data(), m_vertexes.size());
		}
		
		auto alpha = DrawStack::Get()->getTopAlpha() * this->getAlpha();
				
		auto fmt = m_texture?m_texture->format() : ePixel::None;

		ShaderKitPoints::Shared kit = nullptr;

		if(m_shaders) kit = m_shaders(fmt);
		else kit = ShaderKitPoints::SelectorNormal()(fmt);

		Helper::Get()->performBlend(m_blend);
				
		kit->active();

		mRBuffer.v.bind();

		typedef decltype(mRBuffer.v)::VertexData Type;
		kit->bindAttCoord(2, GL::eType::Float, 0, sizeof(Type), Type::OffCoord);
		kit->bindAttColor(4, GL::eType::Float, 0, sizeof(Type), Type::OffColor);
		kit->bindAttPointSize(1, GL::eType::Float, 0, sizeof(Type), Type::OffPointSize);

		kit->setUniColor(Vec4{m_color.red, m_color.green, m_color.blue, m_color.alpha*alpha});
		kit->setUniMat4(this->getMatrix());
		kit->setUniPointSizeRatio(m_size_ratio);
		if(m_texture) kit->setUniSampler(1, m_texture->buffer().getID());

		kit->prepare();
		kit->render(eVMode::Point, 0, mRBuffer.v.count());
		
	}
#elif ZEN_DRAW_METAL
	
	void Particles::initR_()
	{
//		mRBuffer.u.updateSizeRatio(m_size_ratio, m_scale2.x, true);
	}
	
	void Particles::drawR_()
	{
		if(m_is_size_dirty)
		{
			m_is_size_dirty = false;
			this->updateSizeRatio_();
			mRBuffer.u.updateSizeRatio(m_size_ratio, m_scale2.x, false);
		}
		/// update uniform {

		if(mRBuffer.is_dirty || m_vertexes.size() != mRBuffer.v.count())
		{
			mRBuffer.is_dirty = false;
			
			must(sizeof(VertexData) == sizeof(MTVBufIN_P));

			mRBuffer.v.updateBuffer((MTVBufIN_P*)(void*)m_vertexes.data(), m_vertexes.size());
		}

		mRBuffer.u.updateMatrix(getMatrix(), getMatrixVersion(), false);
		
		float alpha = getAlpha() * DrawStack::Get()->getTopAlpha();
		mRBuffer.u.updateColor(getColor(), alpha, m_is_color_dirty);
		m_is_color_dirty = false;
		/// } update uniform
		
		auto blends = Helper::Get()->getBlends(m_blend);

		if( m_texture)
		{
			auto pipe = Metal::ShaderKitPT::GetShared
			(m_is_grey_mode, m_texture->format(), blends);
			kit->render(mRBuffer.u, mRBuffer.v, m_texture->buffer(), eVMode::Point);

		}
		else {

			auto pipe = Metal::ShaderKitPoints::GetShared(m_is_grey_mode, blends);
			kit->render(mRBuffer.u, mRBuffer.v, eVMode::Point);
		}
	}
	
#endif
}}
